-
Notifications
You must be signed in to change notification settings - Fork 1
Feat/#9 msw 초기 설정 및 HydrationBoundaryPage 추가 #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- test category data 추가
WalkthroughMSW 기반 목업 인프라와 Axios 인스턴스가 추가되었습니다. RootLayout이 MSW 초기화 및 Provider로 래핑되고, 서버사이드 React Query 프리패치용 HydrationBoundaryPage가 도입되었습니다. API 경로 상수, tsconfig 경로 변경, turbo globalEnv, .env 무시 항목, MSW 워커 스크립트와 패키지 의존성이 추가되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Browser
participant MSWProvider
participant MSWWorker
participant API
User->>Browser: 페이지 로드
Browser->>MSWProvider: 마운트
MSWProvider->>MSWWorker: initBrowserMSW() 호출
MSWWorker-->>MSWProvider: 준비 완료
Browser->>MSWWorker: GET /categories 요청
MSWWorker-->>Browser: 목 응답(JSON category)
sequenceDiagram
participant Server
participant HydrationBoundaryPage
participant QueryClient
participant API
participant Browser
Server->>HydrationBoundaryPage: 렌더({queries})
HydrationBoundaryPage->>QueryClient: new QueryClient()
HydrationBoundaryPage->>API: prefetchQuery (병렬)
API-->>HydrationBoundaryPage: 응답들
HydrationBoundaryPage-->>Browser: HTML + dehydrate(state)
Browser->>Browser: HydrationBoundary에서 rehydrate
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Nitpick comments (19)
turbo.json (1)
4-4: 모킹 온/오프를 위한 환경변수 추가 고려프리뷰/테스트 환경에서 MSW 모킹을 토글하려면 전용 플래그를 두는 편이 운용에 유리합니다. 예: NEXT_PUBLIC_API_MOCKING. 필요 시 아래처럼 globalEnv에 포함해 주세요.
- "globalEnv": ["NODE_ENV","NEXT_PUBLIC_API_URL"], + "globalEnv": ["NODE_ENV", "NEXT_PUBLIC_API_URL", "NEXT_PUBLIC_API_MOCKING"],.gitignore (1)
27-31: .env.example 템플릿 추가 권장공유 가능한 기본값 템플릿(.env.example)을 커밋해 온보딩과 CI 설정을 명확히 하면 좋습니다. 필요하시면 템플릿 초안까지 만들어드릴게요.
apps/web/app/_constants/path.ts (1)
1-3: 리터럴 보존을 위해 as const 추가 권장경로 상수의 리터럴 특성을 유지하면 오타/변경을 컴파일 타임에 더 잘 포착할 수 있습니다.
export const API_PATH = { CATEGORY: '/categories', -} +} as const추가로, 여러 키가 늘어날 예정이면
satisfies Record<string, \/${string}`>` 제약으로 슬래시 시작을 강제하는 것도 방법입니다.apps/web/app/_mocks/data/category.ts (2)
1-17: 타입 안전성 강화를 위한 리터럴 고정(as const) 제안데이터를 리터럴로 고정해 의도치 않은 변형을 방지하고, 추론된 유니온 타입을 활용할 수 있습니다.
-export const category = [ +export const category = [ { id: 1, name: '치킨', iconKey: 'chicken' }, { id: 2, name: '햄버거', iconKey: 'burger' }, { id: 3, name: '한식', iconKey: 'korean' }, { id: 4, name: '분식', iconKey: 'bunsik' }, { id: 5, name: '중식', iconKey: 'chinese' }, { id: 6, name: '양식', iconKey: 'western' }, { id: 7, name: '샐러드', iconKey: 'salad' }, { id: 8, name: '일식', iconKey: 'japanese' }, { id: 9, name: '카페', iconKey: 'cafe' }, { id: 10, name: '피자', iconKey: 'pizza' }, { id: 11, name: '멕시칸', iconKey: 'mexican' }, { id: 12, name: '아시안', iconKey: 'asian' }, { id: 13, name: '찜·탕', iconKey: 'soup' }, { id: 14, name: '고기·구이', iconKey: 'meat' }, { id: 15, name: '도시락', iconKey: 'lunchbox' }, -] +] as const
1-17: 파생 타입 공개로 재사용성 개선 (선택)소비측 컴포넌트/핸들러에서 타입을 재활용할 수 있도록 파생 타입을 노출하면 편리합니다.
// 파일 하단 등에서 추가 export type Category = typeof category[number]; export type CategoryIconKey = Category['iconKey'];apps/web/app/_lib/axiosInstance.ts (1)
5-5: withCredentials 전역 활성화는 CORS/3rd-party 쿠키 이슈를 유발할 수 있음전역으로 true 설정 시, 크로스 오리진 요청에 예기치 않은 쿠키 전송/차단이 발생할 수 있습니다. 환경변수로 토글하거나, 민감한 요청에서만 opt-in하는 방식을 권장합니다.
예:
withCredentials: process.env.NEXT_PUBLIC_WITH_CREDENTIALS === 'true'apps/web/package.json (1)
9-17: MSW 워커 파일 재생성 스크립트 추가 권장MSW 버전 업데이트 시
mockServiceWorker.js를 재생성하지 않으면 미묘한 불일치가 발생합니다. 스크립트를 추가해 유지보수성을 높이세요.적용 제안 diff:
"scripts": { "dev": "next dev --port 3000 --turbopack", "build": "next build", "start": "next start", "lint": "next lint --max-warnings 0", "check-types": "tsc --noEmit", "test": "vitest run", - "test:watch": "vitest --watch" + "test:watch": "vitest --watch", + "msw:init": "msw init ./public --save" },원하시면 postinstall 훅으로 자동 실행도 가능합니다:
"postinstall": "npm run msw:init".apps/web/app/HydrationBoundaryPage.tsx (4)
1-5: QueryKey 타입을 명시해 쿼리 키 유연성 확보queryKey에 숫자/객체 등이 포함되는 패턴을 지원하려면 string[] 대신 QueryKey를 사용하세요.
적용 제안 diff:
-import { - dehydrate, - HydrationBoundary, - QueryClient, -} from '@tanstack/react-query' +import { + dehydrate, + HydrationBoundary, + QueryClient, + type QueryKey, +} from '@tanstack/react-query'
14-17: QueryConfig의 queryKey 타입을 QueryKey로 교체React Query의 표준 키 타입으로 변경하여 타입 호환성을 개선합니다.
적용 제안 diff:
type QueryConfig = { - queryKey: string[] + queryKey: QueryKey queryFn: () => Promise<unknown> }
45-45: QueryClient 옵션을 Provider와 일치시켜 SSR/CSR 캐싱 일관성 확보QueryClientProvider.tsx에서 staleTime을 60초로 설정하고 있습니다. 여기서도 동일하게 맞추는 것이 안전합니다.
적용 제안 diff:
- const queryClient = new QueryClient() + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 60 * 1000, + }, + }, + })
47-51: Promise.all → allSettled로 변경해 개별 쿼리 실패 시 페이지 전체 실패 방지prefetch 중 하나라도 실패하면 현재는 전체가 reject됩니다. allSettled로 전환하면 실패한 쿼리는 클라이언트에서 재시도 가능하고 페이지는 정상 하이드레이션됩니다.
적용 제안 diff:
- await Promise.all( - queries.map(({ queryKey, queryFn }) => - queryClient.prefetchQuery({ queryKey, queryFn }), - ), - ) + await Promise.allSettled( + queries.map(({ queryKey, queryFn }) => + queryClient.prefetchQuery({ queryKey, queryFn }), + ), + )apps/web/app/_mocks/handlers/index.ts (1)
3-3: handlers에 명시적 타입을 부여해 안정성 향상타입을 명시하면 핸들러 추가/리팩토링 시 오류를 조기에 포착할 수 있습니다.
다음처럼 수정 제안드립니다:
-import { CategoryHandlers } from '@/_mocks/handlers/categoryHandlers' +import type { HttpHandler } from 'msw' +import { CategoryHandlers } from '@/_mocks/handlers/categoryHandlers' -export const handlers = [...CategoryHandlers] +export const handlers: HttpHandler[] = [...CategoryHandlers]apps/web/app/_mocks/handlers/categoryHandlers.ts (3)
11-15: 상대 경로 매칭으로 단순화 가능절대 URL 대신 상대 경로로 정의하면 환경변수 없이도 클라이언트/서버 어디서든 안정적으로 매칭됩니다.
-export const CategoryHandlers = [ - http.get(addBaseUrl(API_PATH.CATEGORY), () => { - return HttpResponse.json(category) - }), -] +export const CategoryHandlers = [ + http.get(API_PATH.CATEGORY, () => { + return HttpResponse.json(category) + }), +]
1-1: 타입 명시로 핸들러 배열 안전성 강화HttpHandler[] 타입을 명시하면 핸들러 형태가 틀어질 때 컴파일 단계에서 잡을 수 있습니다.
-import { http, HttpResponse } from 'msw' +import type { HttpHandler } from 'msw' +import { http, HttpResponse } from 'msw' @@ -export const CategoryHandlers = [ +export const CategoryHandlers: HttpHandler[] = [
2-2: 경로 별칭 사용 일관성 제안이 파일만 상대 경로('../data/category')를 사용하고, 다른 파일은 '@' 별칭을 사용합니다. 팀 컨벤션에 맞춰 통일하면 가독성이 좋아집니다.
-import { category } from '../data/category' +import { category } from '@/_mocks/data/category'apps/web/app/layout.tsx (2)
29-29: 전역 suppressHydrationWarning 사용 축소 권장MSWProvider 수정으로 프로덕션 하이드레이션 미스매치가 해소되므로 전역 억제 플래그는 불필요해집니다. 필요 시 특정 노드 범위로 최소화하세요.
- <html lang='ko' suppressHydrationWarning={true}> + <html lang='ko'>
21-21: async 함수 시그니처는 필요 시에만initServerMSW를 await하지 않는다면 RootLayout을 async로 만들 이유가 없습니다. 위 제안처럼 await를 도입하지 않을 계획이라면 sync로 되돌리는 것이 낫습니다.
-export default async function RootLayout({ +export default function RootLayout({apps/web/app/_mocks/initMSW.ts (2)
13-21: 브라우저 워커 시작을 idempotent하게 만들고 예외 처리 추가 제안개발 모드에서 컴포넌트 마운트/언마운트가 중복 발생할 수 있어
worker.start()의 중복 호출을 방지하는 가드가 있으면 안전합니다. 초기화 실패 시를 대비한 예외 처리도 함께 제안합니다. 또한 반환 타입을 명시해 서버측 함수와 API 일관성을 맞추면 좋습니다.-export const initBrowserMSW = async () => { +export const initBrowserMSW = async (): Promise<void> => { if (typeof window !== 'undefined' && process.env.NODE_ENV === 'development') { - const { worker } = await import('./worker') - await worker.start({ - onUnhandledRequest: 'bypass', - }) - console.log('✅ MSW 브라우저 시작됨') + try { + if ((window as any).__MSW_WORKER_STARTED) return + const { worker } = await import('./worker') + await worker.start({ + onUnhandledRequest: 'bypass', + }) + ;(window as any).__MSW_WORKER_STARTED = true + console.log('✅ MSW 브라우저 시작됨') + } catch (err) { + console.error('❌ MSW 브라우저 시작 실패:', err) + } } }추가로 basePath를 사용하는 경우,
worker.start({ serviceWorker: { url: '<basePath>/mockServiceWorker.js' }})옵션이 필요할 수 있습니다. 필요 시 알려주시면 해당 조건부 설정까지 반영된 패치를 제안드리겠습니다.
17-17: 개발 단계에서 mock 누락을 빠르게 감지하려면 warn 모드 고려현재 브라우저는
onUnhandledRequest: 'bypass'로 설정되어 있어 미모킹 요청이 조용히 통과합니다. 초기 통합 단계에서는'warn'로 두어 누락된 핸들러를 빠르게 잡는 것이 디버깅에 유리합니다. 안정화 이후 다시'bypass'로 되돌릴 수 있습니다.- onUnhandledRequest: 'bypass', + onUnhandledRequest: 'warn',
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (5)
apps/web/public/circles.svgis excluded by!**/*.svgapps/web/public/next.svgis excluded by!**/*.svgapps/web/public/turborepo.svgis excluded by!**/*.svgapps/web/public/vercel.svgis excluded by!**/*.svgpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (16)
.gitignore(1 hunks)apps/web/app/HydrationBoundaryPage.tsx(1 hunks)apps/web/app/_constants/path.ts(1 hunks)apps/web/app/_lib/axiosInstance.ts(1 hunks)apps/web/app/_mocks/MSWProvider.tsx(1 hunks)apps/web/app/_mocks/data/category.ts(1 hunks)apps/web/app/_mocks/handlers/categoryHandlers.ts(1 hunks)apps/web/app/_mocks/handlers/index.ts(1 hunks)apps/web/app/_mocks/initMSW.ts(1 hunks)apps/web/app/_mocks/server.ts(1 hunks)apps/web/app/_mocks/worker.ts(1 hunks)apps/web/app/layout.tsx(2 hunks)apps/web/package.json(3 hunks)apps/web/public/mockServiceWorker.js(1 hunks)apps/web/tsconfig.json(1 hunks)turbo.json(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (8)
apps/web/app/_mocks/server.ts (1)
apps/web/app/_mocks/handlers/index.ts (1)
handlers(3-3)
apps/web/app/_mocks/worker.ts (1)
apps/web/app/_mocks/handlers/index.ts (1)
handlers(3-3)
apps/web/app/_mocks/handlers/categoryHandlers.ts (2)
apps/web/app/_constants/path.ts (1)
API_PATH(1-3)apps/web/app/_mocks/data/category.ts (1)
category(1-17)
apps/web/app/_mocks/initMSW.ts (2)
apps/web/app/_mocks/server.ts (1)
server(4-4)apps/web/app/_mocks/worker.ts (1)
worker(4-4)
apps/web/app/layout.tsx (3)
apps/web/app/_mocks/initMSW.ts (1)
initServerMSW(1-11)apps/web/app/_mocks/MSWProvider.tsx (1)
MSWProvider(6-23)apps/web/app/QueryClientProvider.tsx (1)
QueryProvider(40-57)
apps/web/app/_mocks/MSWProvider.tsx (1)
apps/web/app/_mocks/initMSW.ts (1)
initBrowserMSW(13-21)
apps/web/app/_mocks/handlers/index.ts (1)
apps/web/app/_mocks/handlers/categoryHandlers.ts (1)
CategoryHandlers(11-15)
apps/web/app/HydrationBoundaryPage.tsx (1)
apps/web/app/QueryClientProvider.tsx (3)
QueryProvider(40-57)getQueryClient(26-38)makeQueryClient(12-22)
🔇 Additional comments (12)
turbo.json (1)
4-4: globalEnv 추가 적절 — Turborepo 캐시 일관성 확보NODE_ENV, NEXT_PUBLIC_API_URL를 globalEnv에 명시해 환경 변화가 캐시에 반영되도록 한 점 좋습니다. .env*를 inputs로 포함해 파일 기반 변경도 포착되고 있어 조합이 균형잡혀 있습니다.
.gitignore (1)
27-27: .env 무시 추가 적절루트/패키지 전반의 .env 누락 방지와 비밀정보 유출 차단에 도움이 됩니다.
apps/web/tsconfig.json (2)
13-14: *@/ 경로를 app/로 재매핑 — App Router 구조와 일치App 디렉토리 기준의 import로 정돈되어 Next App Router 구조와 일관성이 맞습니다.
13-14: 모든 ‘@/…’ import 경로 정상 확인됨앱 내
@/경로로 작성된 import가 모두apps/web/app하위 경로에서 문제없이 해석됨을 스크립트로 확인했습니다. 추가 검토나 수정은 필요 없습니다.apps/web/app/_mocks/data/category.ts (1)
1-17: 카테고리 목 데이터 구성 적절UI 목업/개발 편의에 충분한 스키마와 데이터 규모입니다.
apps/web/package.json (2)
6-8: MSW workerDirectory 설정 위치 적절루트 app의 public 경로와 일치합니다. CLI 사용/배포 시 경로 혼동을 줄여줍니다.
23-23: ✅ MSW 패키지 버전 동기화 확인 완료
apps/web/package.json의 devDependencies.msw 버전(2.10.5)과
apps/web/public/mockServiceWorker.js내PACKAGE_VERSION(2.10.5)이 일치합니다.더 이상 조치가 필요하지 않습니다.
apps/web/public/mockServiceWorker.js (1)
10-13: MSW 워커 파일 버전 고정 확인 및 수동 수정 금지PACKAGE_VERSION(2.10.5)와 INTEGRITY_CHECKSUM이 명시되어 있으며, 이 파일은 자동 생성본이므로 수정하지 않는 것이 안전합니다. 패키지 버전 변경 시 반드시 워커 파일을 재생성하세요(예:
npm run msw:init).원하시면 상단 package.json 코멘트에 제공한 검증 스크립트로 버전 동기화를 자동 확인할 수 있습니다.
apps/web/app/_mocks/server.ts (1)
1-4: 서버 런타임 한정 사용 보장 필요(Edge 런타임 회피)
msw/node는 Node 런타임 전용입니다. Next.js에서 Edge(Runtime: 'edge')로 실행되는 경로에선 사용 시 크래시가 날 수 있어, init 쪽에서 반드시 Node 런타임/개발 환경에서만 시작하도록 가드가 필요합니다. AI 요약에 따르면 initMSW에서 dev 전용으로 시작하지만, Edge 런타임 분기(process.env.NEXT_RUNTIME !== 'edge')도 함께 확인해주세요.apps/web/app/_mocks/worker.ts (1)
1-4: 브라우저 워커 초기화 패턴, 깔끔합니다핸들러 집합을 단일 워커로 노출하는 전형적인 구성으로 문제 없습니다. init 쪽에서만 start를 호출하니 중복 실행 위험도 낮습니다.
apps/web/app/_mocks/handlers/index.ts (1)
1-3: 핸들러 집계 모듈 구성 적절함각 도메인별 핸들러를 집계해 단일 진입점으로 노출하는 방식이 유지보수에 유리합니다. 이후 핸들러가 늘어나도 이 파일만 갱신하면 됩니다.
apps/web/app/_mocks/initMSW.ts (1)
3-3: 런타임/환경 게이트 및 동적 import 사용은 적절합니다서버/브라우저 런타임 및 개발 모드에서만 초기화되도록 게이트한 점이 Next.js 15(App Router)와 잘 맞습니다. 번들 오염을 줄이는 동적 import 선택도 적절합니다.
Also applies to: 14-14
| const axiosInstance = axios.create({ | ||
| baseURL: process.env.NEXT_PUBLIC_API_URL, | ||
| withCredentials: true, | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
개발 환경에서 MSW 핸들러 매칭 실패 가능성: baseURL 미설정/상대경로 요청 이슈
현재 baseURL이 환경변수에 전적으로 의존합니다. dev에서 NEXT_PUBLIC_API_URL이 비어 있으면:
- 브라우저: 상대경로('/…')로 호출되어, 절대 URL을 기준으로 작성된 MSW 핸들러와 매칭되지 않을 수 있음.
- 서버(SSR): axios가 상대경로를 처리하지 못해 실패할 수 있음.
해결 방향(택1):
- 서버에서 env 미설정 시 즉시 에러로 fail-fast 처리.
- 브라우저에서는 origin으로 폴백해 항상 절대 URL을 사용.
- 핸들러를 절대 URL이 아닌 상대 경로로 정의(별도 파일 변경 필요).
아래 예시는 서버에서 env 미설정 시 에러를 던지고, 브라우저에서는 origin으로 폴백하며 trailing slash를 정규화합니다. 타임아웃도 기본 추가했습니다.
적용 제안 diff:
-const axiosInstance = axios.create({
- baseURL: process.env.NEXT_PUBLIC_API_URL,
- withCredentials: true,
-})
+const baseURL =
+ process.env.NEXT_PUBLIC_API_URL?.replace(/\/$/, '') ??
+ (typeof window !== 'undefined' ? window.location.origin : undefined)
+
+if (typeof window === 'undefined' && !baseURL) {
+ // SSR에서 상대경로 요청을 방지하기 위해 명시적으로 실패
+ throw new Error('NEXT_PUBLIC_API_URL is required on the server for axiosInstance.')
+}
+
+const axiosInstance = axios.create({
+ baseURL,
+ withCredentials: true,
+ timeout: 10000,
+})📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const axiosInstance = axios.create({ | |
| baseURL: process.env.NEXT_PUBLIC_API_URL, | |
| withCredentials: true, | |
| }) | |
| const baseURL = | |
| process.env.NEXT_PUBLIC_API_URL?.replace(/\/$/, '') ?? | |
| (typeof window !== 'undefined' ? window.location.origin : undefined) | |
| if (typeof window === 'undefined' && !baseURL) { | |
| // SSR에서 상대경로 요청을 방지하기 위해 명시적으로 실패 | |
| throw new Error('NEXT_PUBLIC_API_URL is required on the server for axiosInstance.') | |
| } | |
| const axiosInstance = axios.create({ | |
| baseURL, | |
| withCredentials: true, | |
| timeout: 10000, | |
| }) |
🤖 Prompt for AI Agents
In apps/web/app/_lib/axiosInstance.ts around lines 3 to 6, the axios instance
currently relies solely on NEXT_PUBLIC_API_URL which can be empty in dev causing
MSW handler mismatches or SSR failures; update the file to (1) if running on
server (typeof window === 'undefined') throw an error immediately when
NEXT_PUBLIC_API_URL is not set (fail-fast), (2) when running in browser,
fallback to window.location.origin when NEXT_PUBLIC_API_URL is empty and ensure
the baseURL has no duplicate or missing trailing slash (normalize trailing
slash), and (3) set a sensible default timeout on the axios.create config so
requests won’t hang; implement these checks before calling axios.create and use
the computed absolute baseURL for the instance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
apps/web/app/layout.tsx (1)
26-26: await 무의미: initServerMSW가 내부 비동기(import.then) 완료를 기다리지 않음현재 initServerMSW는 import().then(...) 패턴이라 함수가 즉시 종료되고, 그 사이 SSR fetch가 먼저 실행되면 몇 건이 실제 네트워크로 나갈 수 있습니다. RootLayout에서 await를 사용하더라도 효과가 없습니다. 서버 초기화 함수가 동기적으로 인터셉터를 설치한 후 반환되도록 변경하세요(동일 이슈가 과거 리뷰에서 이미 지적됨).
initMSW.ts 개선 제안:
// apps/web/app/_mocks/initMSW.ts declare global { // eslint-disable-next-line no-var var __MSW_SERVER_STARTED__: boolean | undefined } export const initServerMSW = async () => { // 서버 사이드 + 개발 환경에서만 동작 if (typeof window !== 'undefined' || process.env.NODE_ENV !== 'development') return if (globalThis.__MSW_SERVER_STARTED__) return // import 완료를 기다린 뒤 listen을 즉시 수행(동기 설치) const { server } = await import('./server') server.listen({ onUnhandledRequest: 'warn' }) globalThis.__MSW_SERVER_STARTED__ = true console.log('✅ MSW 서버 시작됨') }
🧹 Nitpick comments (4)
apps/web/app/_mocks/MSWProvider.tsx (3)
10-17: MSW 초기화 실패 시 무한 빈 화면 가능 — try/catch 및 cleanup 추가 제안initBrowserMSW()가 실패하면 setMSWReady(true)가 호출되지 않아 개발 환경에서 화면이 영구히 null로 유지될 수 있습니다. 또한 HMR/언마운트 타이밍에 setState 호출 경고를 피하기 위한 cleanup도 권장합니다.
아래처럼 예외를 삼키고 계속 렌더하도록 보완해 주세요.
useEffect(() => { - if (!isDev || mswReady) return - const init = async () => { - await initBrowserMSW() - setMSWReady(true) - } - init() + if (!isDev || mswReady) return + let cancelled = false + const init = async () => { + try { + await initBrowserMSW() + } catch (e) { + console.warn('MSW 초기화 실패, 앱을 계속 렌더링합니다.', e) + } finally { + if (!cancelled) setMSWReady(true) + } + } + init() + return () => { + cancelled = true + } }, [isDev, mswReady])
4-4: React StrictMode 중복 start 방지 가드 권장(개발 편의성)개발 모드 StrictMode에서 effect가 두 번 호출되어 worker.start가 중복 호출될 수 있습니다. initBrowserMSW 내부에 전역 가드를 둬서 안전하게 1회만 시작하도록 보완하는 것을 권장합니다.
apps/web/app/_mocks/initMSW.ts 제안:
// initMSW.ts declare global { // eslint-disable-next-line no-var var __MSW_BROWSER_STARTED__: boolean | undefined } export const initBrowserMSW = async () => { if ( typeof window === 'undefined' || process.env.NODE_ENV !== 'development' || globalThis.__MSW_BROWSER_STARTED__ ) { return } const { worker } = await import('./worker') await worker.start({ onUnhandledRequest: 'bypass' }) globalThis.__MSW_BROWSER_STARTED__ = true console.log('✅ MSW 브라우저 시작됨') }
6-6: 개발 중 빈 화면 대신 fallback 제공 옵션 제안개발 환경에서 초기 한 틱 동안 null을 렌더링하는 대신, 선택적으로 스켈레톤/로딩 UI를 전달할 수 있도록 fallback prop 지원을 제안합니다.
-export function MSWProvider({ children }: { children: React.ReactNode }) { +export function MSWProvider({ + children, + fallback, +}: { + children: React.ReactNode + fallback?: React.ReactNode +}) { @@ - if (!mswReady) return null + if (!mswReady) return <>{fallback ?? null}</>Also applies to: 19-21
apps/web/app/layout.tsx (1)
29-29: suppressHydrationWarning는 개발 환경에서만 사용하는 것을 권장현재 전역으로 억제하면 프로덕션에서 실제 하이드레이션 문제를 가릴 수 있습니다. dev에서만 켜는 형태가 안전합니다.
-export default async function RootLayout({ +export default async function RootLayout({ children, }: { children: React.ReactNode }) { await initServerMSW() - return ( - <html lang='ko' suppressHydrationWarning={true}> + const suppress = process.env.NODE_ENV === 'development' + return ( + <html lang='ko' suppressHydrationWarning={suppress}>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/web/app/_mocks/MSWProvider.tsx(1 hunks)apps/web/app/_mocks/handlers/categoryHandlers.ts(1 hunks)apps/web/app/_mocks/initMSW.ts(1 hunks)apps/web/app/layout.tsx(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/web/app/_mocks/handlers/categoryHandlers.ts
- apps/web/app/_mocks/initMSW.ts
🧰 Additional context used
🧬 Code Graph Analysis (2)
apps/web/app/layout.tsx (3)
apps/web/app/_mocks/initMSW.ts (1)
initServerMSW(1-11)apps/web/app/_mocks/MSWProvider.tsx (1)
MSWProvider(6-22)apps/web/app/QueryClientProvider.tsx (1)
QueryProvider(40-57)
apps/web/app/_mocks/MSWProvider.tsx (1)
apps/web/app/_mocks/initMSW.ts (1)
initBrowserMSW(13-21)
🔇 Additional comments (2)
apps/web/app/_mocks/MSWProvider.tsx (1)
7-17: 개발 환경에서만 MSW 게이팅하도록 한 점 LGTM프로덕션에서는 즉시 children을 렌더링하고, 개발에서만 MSW 준비 완료 후 렌더링하도록 한 로직이 SSR/CSR 하이드레이션 이슈를 잘 회피합니다. 이전 리뷰 우려 사항이 해결되었습니다.
apps/web/app/layout.tsx (1)
31-39: 컴포넌트 트리 구성 LGTMMSWProvider → QueryProvider 순서로 감싸는 구성은 브라우저 MSW 준비 이후에 React Query가 동작하도록 만들어, 개발 환경에서의 데이터 플로우를 안정화합니다.
#️⃣연관된 이슈
📝작업 내용
Next.js 15 App Router 환경에서 API Mocking을 위한 MSW(Mock Service Worker) 설정을 추가했습니다.
initServerMSW: Server Component, API Routes에서 fetch 요청 처리initBrowserMSW: Client Component에서 api 요청 처리 (Service Worker 활용)React Query의 서버 prefetching과 클라이언트 hydration 로직을 재사용 가능한 HydrationBoundaryPage 컴포넌트로 추상화했습니다.
HydrationBoundaryPage컴포넌트 추가 0592285queries배열로 여러 쿼리 동시 prefetchPromise.all을 활용해 병렬로 쿼리 처리스크린샷 (선택)
💬리뷰 요구사항(선택)
Summary by CodeRabbit
New Features
Chores